/*
 * file:         passwd.c description:  change user password author:
 * Shane Kerr <kerr@wizard.net> copyright:    1998 by Shane Kerr
 * <kerr@wizard.net>, under terms of GPL
 */

/* todo:  make executable setuid on execute */
/* needs to check setuid bit in fs/exec.c of ELKS kernel */
/* todo:  add a set of rules to make sure trivial passwords are not used */
/* todo:  lock password file while updating */
/* need file locking first */
/* todo:  use rename to do an atomic move of temporary file */
/* need rename that overwrites first */

#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

/* maximum errors */
#define MAX_FAILURE 5

/* valid characters for a salt value */
char            salt_char [] =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./";

int
main(int argc, char **argv)
{
    struct passwd  *pwd;
    char           *pbuf, salt[3];
    char            nbuf1  [128], nbuf2[128];
    time_t          now;
    int             ch;
    int             failure_cnt;
    FILE           *passwd_fp;
    char           *tmp_fname;
    int             uid;

    switch (argc) {
    case 1:
        pwd = getpwuid(getuid());
        if (pwd == NULL) {
            fprintf(stderr, "%s: Error reading password file\n", argv[0]);
            return 1;
        }
        break;
    case 2:
        pwd = getpwnam(argv[1]);
        if (pwd == NULL) {
            fprintf(stderr, "%s: Unknown user %s\n", argv[0], argv[1]);
            return 1;
        }
        if ((getuid() != 0) && (getuid() != pwd->pw_uid)) {
            fprintf(stderr, "You may not change the password for %s.\n",
                    argv[1]);
            return 1;
        }
        break;
    default:
        fprintf(stderr, "usage: %s [ name ]\n", argv[0]);
        return 1;
    }

    printf("Changing password for %s\n", pwd->pw_name);

    if ((getuid() != 0) && (pwd->pw_passwd[0] != 0)) {
        pbuf = getpass("Old password:");
        putchar('\n');
        salt[0] = pwd->pw_passwd[0];
        salt[1] = pwd->pw_passwd[1];
        salt[2] = 0;
        if (strcmp(crypt(pbuf, salt), pwd->pw_passwd)) {
            fprintf(stderr, "Incorrect password for %s.\n", pwd->pw_name);
            return 1;
        }
        memset(pbuf, 0, strlen(pbuf));
    }
    failure_cnt = 0;
    for (;;) {
        pbuf = getpass("New password:");
        putchar('\n');
        strcpy(nbuf1, pbuf);
        memset(pbuf, 0, strlen(pbuf));
        pbuf = getpass("Re-enter new password:");
        putchar('\n');
        strcpy(nbuf2, pbuf);
        memset(pbuf, 0, strlen(pbuf));
        if (strcmp(nbuf1, nbuf2) == 0) {
            /* I'm just going to use the LSB of time for salt */
            time(&now);
            ch = salt_char[now & 0x3F];
            salt[0] = ch;
            ch = salt_char[(now >> 6) & 0x3F];
            salt[1] = ch;
            salt[2] = 0;

            /* need to create a temporary file for the new password */
            tmp_fname = tmpnam(NULL);
            if (tmp_fname == NULL) {
                perror("Error getting temporary file name");
                return 1;
            }
            passwd_fp = fopen(tmp_fname, "w");
            if (passwd_fp == NULL) {
                fprintf(stderr, "Error creating temporary password file\n");
                return 1;
            }
            /* save off our UID */
            uid = pwd->pw_uid;

            /* save entries to the new file */
            setpwent();
            pwd = getpwent();
            while (pwd != NULL) {
                if (pwd->pw_uid == uid) {
                    pwd->pw_passwd = crypt(nbuf1, salt);
                }
                if (putpwent(pwd, passwd_fp) == -1) {
                    perror("Error writing password file");
                    return 1;
                }
                pwd = getpwent();
            }

            /* update the file and move it into position */
            fclose(passwd_fp);
            if (unlink("/etc/passwd") == -1) {
                perror("Error removing old password file");
                return 1;
            }
            /* can't use rename() since unimplemented on FAT fs */
            sprintf(nbuf1, "mv %s /etc/passwd", tmp_fname);
            if (system(nbuf1) != 0) {
                perror("Error installing new password file");
                return 1;
            }
            /* celebrate our success */
            printf("Password changed.\n");
            return 0;
        }
        if (++failure_cnt >= MAX_FAILURE) {
            fprintf(stderr, "The password for %s is unchanged.\n", pwd->pw_name);
            return 1;
        }
        fprintf(stderr, "They don't match; try again.\n");
    }
}
